Imports System.Drawing
Imports System.Drawing.Drawing2D

Namespace Annotations

  ''' <summary>
  ''' Contains information about the drawing unit and the minimal Pen and Font size so
  ''' that lines and text remain properly visible on screen.
  ''' </summary>
  ''' <remarks></remarks>
  Public Class DrawingInfo
    ''' <summary>
    ''' Multiply this value with the line or font size to obtain the proper drawing size, taking the 
    ''' scaling of the graphics object already into account.
    ''' </summary>
    ''' <remarks></remarks>
    Public Unit As Single
    Public MinPenSize As Single
    Public MinFontSize As Single
    Public MinPointSize As Integer

    ''' <summary>
    ''' Compute the real drawing width from the desired line width (which can be in screen or image dimensions).
    ''' </summary>
    ''' <param name="lineWidth"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetDrawingLineWidth(ByVal lineWidth As Integer) As Single
      Return CSng(Math.Max(lineWidth * Me.Unit, Me.MinPenSize))
    End Function


    ''' <summary>
    ''' Compute the real drawing width from the desired drawing line width, taking screen visibility into account.
    ''' </summary>
    ''' <param name="drawingLineWidth "></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetDrawingLineWidth(ByVal drawingLineWidth As Single) As Single
      Return CSng(Math.Max(drawingLineWidth, Me.MinPenSize))
    End Function

    ''' <summary>
    ''' Compute the real drawing point rectangle size from the desired point size (which can be in screen or image dimensions).
    ''' </summary>
    ''' <param name="pointSize"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetDrawingPointRectangleSize(ByVal pointSize As Integer) As Single
      Return CSng(Math.Max(pointSize * Me.Unit, Me.MinPointSize))
    End Function

    ''' <summary>
    ''' Compute the real drawing font size from the desired font size (which can be in screen or image dimensions).
    ''' </summary>
    ''' <param name="fontSize"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetDrawingFontSize(ByVal fontSize As Integer) As Single
      Return CSng(Math.Max(fontSize * Me.Unit, Me.MinFontSize))
    End Function

    ''' <summary>
    ''' Return a fictitious pen used when selecting a line. It's a bit wider than the actual drawing pen of that line to help selection.
    ''' </summary>
    ''' <param name="width"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overloads Function GetSelectionPen(ByVal width As Integer) As Pen
      Return GetPen(GetDrawingLineWidth(width) + 2, Color.Black)
    End Function

    Public Overloads Function GetPen(ByVal width As Integer, ByVal col As Color) As Pen
      Return GetPen(GetDrawingLineWidth(width), col)
    End Function

    Public Overloads Function GetPen(ByVal width As Single, ByVal col As Color) As Pen
      Dim p As New Pen(col)
      p.LineJoin = LineJoin.Round
      p.EndCap = LineCap.Round
      p.StartCap = LineCap.Round
      p.Width = width
      Return p
    End Function

    Public Function GetSelectedPen1(ByVal width As Integer) As Pen
      Return GetSelectedPen1(GetDrawingLineWidth(width))
    End Function

    Public Function GetSelectedPen1(ByVal width As Single) As Pen
      Dim p As New Pen(Drawing.Color.Black)
      Dim sPattern() As Single = {3, 3}
      p.DashPattern = sPattern
      p.LineJoin = LineJoin.Round
      p.EndCap = LineCap.Round
      p.StartCap = LineCap.Round
      p.Width = width
      Return p
    End Function

    Public Function GetSelectedPen2(ByVal width As Integer) As Pen
      Return GetSelectedPen2(GetDrawingLineWidth(width))
    End Function

    Public Function GetSelectedPen2(ByVal width As Single) As Pen
      Dim p As New Pen(Drawing.Color.White)
      Dim sPattern() As Single = {3, 3}
      p.DashPattern = sPattern
      p.DashOffset = p.DashPattern(0)
      p.LineJoin = LineJoin.Round
      p.EndCap = LineCap.Round
      p.StartCap = LineCap.Round
      p.Width = width
      Return p
    End Function

  End Class

  ''' <summary>
  ''' How to interpret line and text drawing dimensions
  ''' </summary>
  ''' <remarks></remarks>
  Public Enum LineAndTextDimensionMode
    None = 0
    ''' <summary>
    ''' Dimensions are relative to the image, so zooming affects them.
    ''' </summary>
    ''' <remarks></remarks>
    Image
    ''' <summary>
    ''' Dimensions are relative to the screen, so they are unaffected by zooming.
    ''' </summary>
    ''' <remarks></remarks>
    Screen
  End Enum

  ''' <summary>
  ''' Mode defining how individual regions can be combined
  ''' </summary>
  ''' <remarks>
  ''' </remarks>
  Public Enum RegionAddMode
    None = 0
    Union
    Intersect
    XorMode 'Default
    Complement
  End Enum

  ''' <summary>
  ''' The event arguments passed when a collection changes
  ''' </summary>
  ''' <remarks>
  ''' </remarks>
  Public Class CollectionChangedEventArgs
    Inherits System.EventArgs

    ''' <summary>
    ''' The possible types of event
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    Public Enum EventType
      ItemSet
      ItemsSet
      ItemAdded
      ItemsAdded
      ItemDeleted
      ItemsDeleted
      PropertySet
      Updated
    End Enum

    ''' <summary>
    ''' Create a new event of given type, regarding the given object at the given index in the collection.
    ''' </summary>
    ''' <param name="eventType"></param>
    ''' <param name="item"></param>
    ''' <param name="index"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal eventType As EventType, ByVal item As Object, ByVal index As Integer)
      _type = eventType
      _item = item
      _index = index
    End Sub

    ''' <summary>
    ''' Create a new event of given type, regarding the given object.
    ''' </summary>
    ''' <param name="eventType"></param>
    ''' <param name="item"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal eventType As EventType, ByVal item As Object)
      _type = eventType
      _item = item
      _index = -1
    End Sub

    ''' <summary>
    ''' Create a new event of given type, regarding the object at the given index in the collection.
    ''' </summary>
    ''' <param name="eventType"></param>
    ''' <param name="index"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal eventType As EventType, ByVal index As Integer)
      _type = eventType
      _item = Nothing
      _index = index
    End Sub

    ''' <summary>
    ''' Create a new event of given type.
    ''' </summary>
    ''' <param name="eventType"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal eventType As EventType)
      _type = eventType
      _item = Nothing
      _index = -1
    End Sub

    Public Sub New()
    End Sub

    Protected _type As EventType
    ''' <summary>
    ''' The type of event
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public Property Type() As EventType
      Get
        Return _type
      End Get
      Set(ByVal value As EventType)
        _type = value
      End Set
    End Property

    Protected _item As Object
    ''' <summary>
    ''' The item being added or deleted. 
    ''' </summary>
    ''' <value></value>
    ''' <remarks>Is nothing when the event has modified more than one item.
    ''' </remarks>
    Public Property Item() As Object
      Get
        Return _item
      End Get
      Set(ByVal value As Object)
        _item = value
      End Set
    End Property

    Protected _index As Integer
    ''' <summary>
    ''' The index of the modified item in the collection.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>Can be -1 when not relevant
    ''' </remarks>
    Public Property Index() As Integer
      Get
        Return _index
      End Get
      Set(ByVal value As Integer)
        _index = value
      End Set
    End Property
  End Class

  ''' <summary>
  ''' Template for a collection with events
  ''' </summary>
  ''' <typeparam name="T"></typeparam>
  ''' <remarks>Uses a generic list, and also overrides the default keyvaluepair enumerator with an enumerator over T.</remarks>
  Public MustInherit Class CollectionWithEventsTemplate(Of T As Class)
    Inherits Generic.Dictionary(Of String, T)
    Implements Generic.IEnumerable(Of T), ISelectable

#Region "Events"
    ''' <summary>
    ''' Triggered when the collection or a property changes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event Changed(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)

    ''' <summary>
    ''' Allows raising this event from derived classes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks>As an alternative to handling the base class event in a derived class, this event handler can also be overriden</remarks>
    Protected Friend Overridable Sub OnChangedEvent(ByVal sender As Object, ByVal e As CollectionChangedEventArgs)
      If Me._events Then RaiseEvent Changed(sender, e)
    End Sub

    Public Event SelectedChanged(ByVal sender As ISelectable) Implements ISelectable.SelectedChanged
    ''' <summary>
    ''' Allows raising this event from derived classes.
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <remarks>As an alternative to handling the base class event in a derived class, this event handler can also be overriden</remarks>
    Protected Friend Overridable Sub OnSelectedChangedEvent(ByVal sender As ISelectable)
      If Me._events Then RaiseEvent SelectedChanged(sender)
    End Sub
#End Region

#Region "Constructor - destructor"
    ''' <summary>
    ''' Constructor.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
      MyBase.New(16)
    End Sub
#End Region

#Region "Properties"
    Private _events As Boolean = True
    ''' <summary>
    ''' Enable or disable raising events. Default is true
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Careful, setting this to false may speed up things but may also create mayhem when drawing.</remarks>
    Public Property Events() As Boolean
      Get
        Return Me._events
      End Get
      Set(ByVal value As Boolean)
        Me._events = value
      End Set
    End Property

    ''' <summary>
    ''' Get or set an item. Raise event when setting an element; itemSet if the key already exists, itemAdd if not.
    ''' </summary>
    ''' <param name="key"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Friend Shadows Property Item(ByVal key As String) As T
      Get
        Return MyBase.Item(key)
      End Get
      Set(ByVal value As T)
        If Me.ContainsKey(key) Then
          MyBase.Item(key) = value
          OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemSet))
        Else
          MyBase.Item(key) = value
          OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemAdded))
        End If
      End Set
    End Property

    ''' <summary>
    ''' Return the item at given index. 
    ''' </summary>
    ''' <param name="index"></param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>This is not efficient at all, do not use in a loop, use for each construct whenever possible!</remarks>
    Protected Friend Shadows ReadOnly Property Item(ByVal index As Integer) As T
      Get
        If Me.Count > index Then
          Dim en As Generic.IEnumerator(Of T) = Me.GetEnumerator()
          For i As Integer = 0 To index
            en.MoveNext()
          Next
          Return en.Current
        Else
          Return Nothing
        End If
      End Get
    End Property

    Dim _selected As Boolean = False
    ''' <summary>
    ''' True if selected.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    Public Property Selected() As Boolean Implements ISelectable.Selected
      Get
        Return Me._selected
      End Get
      Set(ByVal value As Boolean)
        Me._selected = value
        Me.OnSelectedChangedEvent(Me)
      End Set
    End Property
#End Region

#Region "Drawing and related properties"
    Private _visible As Boolean = True
    ''' <summary>
    ''' Determines if this object will be drawn or not.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public Property Visible() As Boolean
      Get
        Return _visible
      End Get
      Set(ByVal Value As Boolean)
        _visible = Value
        Me.OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.PropertySet))
      End Set
    End Property

    Private _enabled As Boolean = True
    ''' <summary>
    ''' Determines if the object will interact with the user using the mouse.
    ''' </summary>
    ''' <value></value>
    ''' <remarks>
    ''' </remarks>
    Public Property Enabled() As Boolean
      Get
        Return _enabled
      End Get
      Set(ByVal Value As Boolean)
        _enabled = Value
        Me.OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.PropertySet))
      End Set
    End Property
#End Region

#Region "Methods"

    ''' <summary>
    ''' Copy to array.
    ''' </summary>
    ''' <param name="ar"></param>
    ''' <remarks></remarks>
    Public Sub CopyTo(ByVal ar() As T)
      Me.Values.CopyTo(ar, 0)
    End Sub

    ''' <summary>
    ''' Allows using 'for each' over the value collection directly, without having to reference the collection.values
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>This hides the default.NET enumerator, which iterates over the KeyValuePair ...</remarks>
    Public Shadows Function GetEnumerator() As Generic.IEnumerator(Of T) Implements Generic.IEnumerable(Of T).GetEnumerator
      Return Me.Values.GetEnumerator()
    End Function

    ''' <summary>
    ''' Add an element with given key, and raise the changed event. Causes error if key already exists, use item if in doubt.
    ''' </summary>
    ''' <param name="key"></param>
    ''' <param name="value"></param>
    ''' <remarks></remarks>
    Public Overridable Shadows Sub Add(ByVal key As String, ByVal value As T)
      MyBase.Add(key, value)
      OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemAdded, value, Me.Count - 1))
    End Sub

    ''' <summary>
    ''' Add element to collection using a key to which an automatically incremented index is added.
    ''' Very useful to avoid key clashes in the collection, but slower.
    ''' </summary>
    ''' <param name="value"></param>
    ''' <param name="keyPrefix"></param>
    ''' <remarks></remarks>
    Public Overridable Sub AddWithSequentialKey(ByVal keyPrefix As String, ByVal value As T)
      Static newIndex As Integer = 0
      If Me.ContainsKey(keyPrefix & newIndex) Then
        'newIndex is not valid, define new starting index
        Dim index As Integer = 0
        Do
          index += 1
        Loop Until Me.ContainsKey(keyPrefix & index) = False
        newIndex = index
      Else
        'NewIndex is valid, but might be way too high!
        If newIndex > 0 Then
          Dim index As Integer = newIndex
          Do
            index -= 1
          Loop Until Me.ContainsKey(keyPrefix & index) Or index = 0

          If index = 0 Then
            If Me.ContainsKey(keyPrefix & index) Then
              newIndex = 1
            Else
              newIndex = 0
            End If
          Else
            newIndex = index + 1
          End If
        End If
      End If
      Me.Add(keyPrefix & newIndex, value)
      newIndex += 1
    End Sub

    ''' <summary>
    ''' Add the elements of collection of type T. Use a keyprefix and a sequential number.
    ''' </summary>
    ''' <param name="keyPrefix"></param>
    ''' <param name="items"></param>
    ''' <remarks>Does not check uniqueness of keys as this would be too slow: any existing key with the given prefix can be overwritten!
    ''' We do not use the AddRange methods of the base class, as we may need to add event handlers to each element.</remarks>
    Public Overridable Shadows Sub AddRange(ByVal keyPrefix As String, ByVal items As Generic.IEnumerable(Of T))
      Dim index As Integer = 1
      Dim doEvents As Boolean = Me._events
      Me._events = False
      For Each i As T In items
        Me.Add(keyPrefix & index, i)
      Next
      Me._events = doEvents
      OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemsAdded))
    End Sub

    ''' <summary>
    ''' Add the elements of collection of KeyValuePairs.
    ''' </summary>
    ''' <param name="items"></param>
    ''' <remarks>Does not check uniqueness of keys as this would be too slow: any existing key can be overwritten.
    ''' We do not use the AddRange methods of the base class, as we may need to add event handlers to each element.</remarks>
    Public Overridable Shadows Sub AddRange(ByVal items As Generic.Dictionary(Of String, T))
      Dim en As Enumerator = MyBase.GetEnumerator
      Dim doEvents As Boolean = Me._events
      Me._events = False
      While en.MoveNext
        Me.Add(en.Current.Key, en.Current.Value)
      End While
      Me._events = doEvents
      OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemsAdded))
    End Sub

    ''' <summary>
    ''' Remove an item given its value
    ''' </summary>
    ''' <param name="value"></param>
    ''' <remarks></remarks>
    Public Overridable Overloads Sub Remove(ByVal value As T)
      Dim key As String = Me.FindValueKey(value)
      If Not key Is Nothing Then
        Me.Remove(key)
      End If
    End Sub

    ''' <summary>
    ''' Remove an item given its key
    ''' </summary>
    ''' <param name="key"></param>
    ''' <remarks>No error is raised if the key does not exist!</remarks>
    Public Overridable Overloads Sub Remove(ByVal key As String)
      If MyBase.Remove(key) Then OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemDeleted))
    End Sub

    ''' <summary>
    ''' Clear the collection
    ''' </summary>
    ''' <remarks></remarks>
    Public Overridable Shadows Sub Clear()
      MyBase.Clear()
      OnChangedEvent(Me, New CollectionChangedEventArgs(CollectionChangedEventArgs.EventType.ItemsDeleted))
    End Sub

    ''' <summary>
    ''' Find key of a certain value. Not sure if this is a good idea, it's bound to be slow!
    ''' </summary>
    ''' <param name="value"></param>
    ''' <returns></returns>
    ''' <remarks>We cannot use for each here, but need to obtain the base class enumerator. </remarks>
    Public Function FindValueKey(ByVal value As T) As String
      Dim comparer As Generic.EqualityComparer(Of T)
      comparer = Generic.EqualityComparer(Of T).Default
      Dim en As Enumerator = MyBase.GetEnumerator
      While en.MoveNext
        If comparer.Equals(en.Current.Value, value) Then
          Return en.Current.Key
        End If
      End While
      Return Nothing
    End Function

    ''' <summary>
    ''' Find key at a certain index. Not sure if this is a good idea, it's bound to be slow!
    ''' </summary>
    ''' <param name="index"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function FindIndexKey(ByVal index As Integer) As String
      Dim en As Enumerator = MyBase.GetEnumerator
      Dim i As Integer = 0
      While en.MoveNext
        If i = index Then
          Return en.Current.Key
        End If
        i += 1
      End While
      Return Nothing
    End Function
#End Region
  End Class

End Namespace
